#include "DXUT.h"

#include "Region.h"

#include <vector>

using namespace std;

const float g_LightLevels[] = {
	0.0f,
	0.00678223072f,
	0.0096889104f,
	0.0138412872f,
	0.01977326743f,
	0.0282475249f,
	0.040353607f,
	0.05764801f,
	0.0823543f,
	0.117649f,
	0.16807f,
	0.2401f,
	0.343f,
	0.49f,
	0.7f,
	1.0f,
};

float Chunk::getTemperature(int x, int z) {
	return pRegion->getTemperature(x + _x, z + _z);
}

float Chunk::getHumidity(int x, int z) {
	return pRegion->getHumidity(x + _x, z + _z);
}

BYTE Chunk::getBiome(int x, int z) {
	return pRegion->getBiome(x, z);
}

Chunk::~Chunk() {
#ifdef RENDER
	SAFE_RELEASE( pVertexBuffer );
	SAFE_RELEASE( pIndexBuffer );
#endif

	free(pBlocks);
}

Chunk::Chunk(IWorld* region, int x, int z, int y) {
	_x = x;
	_z = z;
	_y = y;
	rx = abs(x % 16);
	rz = abs(z % 16);

	pRegion  = region;

	pBlocks = (Block*) malloc (4096 * sizeof(Block));
	ZeroMemory(pBlocks, 4096 * sizeof(Block));
	if (pBlocks == NULL) exit (1);

#ifdef RENDER
	pVertexBuffer	= NULL;
	pIndexBuffer	= NULL;	

	empty			= true;

	indexSize[0]			= 0;
	indexSize[1]			= 0;
	vbSize					= 0;
	ibSize					= 0;

#endif
}

BYTE * Chunk::chunkGetMemory() {
	return (BYTE*)pBlocks;
}

void Chunk::chunkSetMemory(BYTE * data) {
	memcpy(pBlocks, data, 4096 * sizeof(Block));
}

inline char maxLight(char size, char * ary) {
	char l = 0;
	for (int i = 0; i < size; i++) {
		l = max(l, ary[i]);
	}
	return l;
}

void Chunk::calculateLight() {
	for (int x = 0; x < 16; ++x) {
		for (int z = 0; z < 16; ++z) {
			for (int y = 0; y < 16; ++y) {

				int i = x + (z << 4) + (y << 8);
				if (!(pBlocks[i].status & SOLID)) {
					Block blocks[3] = {
						getBlock(x-1, z, y),
						getBlock(x, z-1, y),
						getBlock(x, z, y-1),
					};

					char ldv = (pBlocks[i].status & LDV) + 1; 

					char lights[4] = {pBlocks[i].skyLight,
						blocks[0].skyLight - ldv,
						blocks[1].skyLight - ldv,
						blocks[2].skyLight - ldv};

					pBlocks[i].skyLight = maxLight(4, lights);

					char blights[4] = {pBlocks[i].blockLight,
						blocks[0].blockLight - ldv,
						blocks[1].blockLight - ldv,
						blocks[2].blockLight - ldv};

					pBlocks[i].blockLight = maxLight(4, blights);
				}
			}
		}
	}
}

void Chunk::calculateLight2() {
	for (int x = 15; x >= 0; --x) {
		for (int z = 15; z  >= 0; --z) {
			for (int y = 15; y >= 0; --y) {

				int i = x + (z << 4) + (y << 8);
				if (!(pBlocks[i].status & SOLID)) {
					Block blocks[3] = {
						getBlock(x+1, z, y),
						getBlock(x, z+1, y),
						getBlock(x, z, y+1)
					};

					char lup = blocks[2].skyLight;
					if (lup < 15) lup--;
					char ldv = (pBlocks[i].status & LDV) + 1; 

					char lights[4] = {pBlocks[i].skyLight,
						blocks[0].skyLight - ldv,
						blocks[1].skyLight - ldv,
						lup - ldv + 1};

					pBlocks[i].skyLight = maxLight(4, lights);

					char blights[4] = {pBlocks[i].blockLight,
						blocks[0].blockLight - ldv,
						blocks[1].blockLight - ldv,
						blocks[2].blockLight - ldv};

					pBlocks[i].blockLight = maxLight(4, blights);
				}
			}
		}
	}
}

void Chunk::resetLight(byte *height) {
	for (int z = 0; z < 16; ++z) {
		for (int x = 0; x < 16; ++x) {
			char light = 15;
			byte * h = height + x + (z << 4);
			if ( *h == 0) 
				for (int y = 15; y >= 0; y--) {
					int i = x + (z << 4) + (y << 8);
					if (light && (pBlocks[i].blockID != AIR)) {
						*h = static_cast<byte>(y + _y);
						light = 0;
					}
					pBlocks[i].skyLight = light;
				}

		}
	}
}

void Chunk::generateBlocks() {
	/* DEPRICATED */
	for (int i = 0; i < BLOCKCOUNT; i++) {
		if ((rand() % 7) >= 1) {				
			pBlocks[i].blockID = 1;
		} else {
			pBlocks[i].blockID = 0;
		}
	}
}

void Chunk::generateBlocks(byte *height, Cavemap * cave) {
	for (int x = 0; x < 16; ++x) {
		for (int z = 0; z < 16; ++z) {
			for (int y = 15; y >= 0; --y) {
				// get height
				int h = height[x + (z << 4)] - _y;
				// get index
				int i = x + (z << 4) + (y << 8);

				// Air if to high
				if (y > h) {
					//	if (y + _y < 50) {
					//		pBlocks[i] = STILLWATER_BLOCK;
					//	} else {
					pBlocks[i] = SKY_BLOCK;
					//	}
					// gen blocks else
				} else {
					//cave ...
					if (cave->isCave((_x + x) % 256, (_z + z) % 256, _y + y)) {
						pBlocks[i] = AIR_BLOCK;
						if (y == h) {
							height[x + (z << 4)] = height[x + (z << 4)] - 1;
						}
						//pBlocks[i].skyLight   = getBlock(x, z, y+1).skyLight;
					} else if (y < (h - 2)) {
						//if ((rand() % 512) == 1) {
						//	pBlocks[i] = GLOWSTONE_BLOCK;
						//} else {
						pBlocks[i] = STONE_BLOCK;//.blockID = 3;	
						//}
					} else {
						if (y + _y <= 50) {
							if ((y + _y > 47) || (rand() % 3 > 0 )) {
								pBlocks[i] = SAND_BLOCK;
							} else { 
								pBlocks[i] = DIRT_BLOCK;
							}
						} else {
							if (y == h) {
								pBlocks[i] = GRASS_BLOCK;
							} else {
								pBlocks[i] = DIRT_BLOCK;
							}
						}
					}
				}
			}
		}
	}
}

inline void calcLight(genBuffer ml, genBuffer l1, genBuffer l2, genBuffer dl, float * s, float * b) {
#if FALSE
	*b	= g_LightLevels[ml.blocklight];
	*s	= g_LightLevels[ml.skylight];
#else
	float bl	= g_LightLevels[ml.blocklight];
	float sl	= g_LightLevels[ml.skylight];
	float c		= 1.0f;

	if (l1.face) {
		bl += g_LightLevels[l1.blocklight];
		sl += g_LightLevels[l1.skylight];
		c += 1.0f;
	}
	if (l2.face) {
		bl += g_LightLevels[l2.blocklight];
		sl += g_LightLevels[l2.skylight];
		c += 1.0f;
	}
	if (dl.face && (l1.face || l2.face)) {
		bl += g_LightLevels[dl.blocklight];
		sl += g_LightLevels[dl.skylight];
		c += 1.0f;
	} 

	bl /= c;
	sl /= c;

	if (!l1.face | !l2.face | !dl.face) { bl *= 0.6f; sl *= 0.6f; }

	*b = bl;
	*s = sl;
#endif
}

inline int coord(int x, int z, int y) {
	return x + z * 18 + y * 324;
}

inline void setLight(int x, int z, int y, char facing, genBuffer * buffer, float * c, float * b) {
	if (facing  & (FACELEFT | FACERIGHT)) {
		/* LEFT / RIGH */
		calcLight(buffer[coord(x, z, y)], 
			buffer[coord(x, z - 1, y)], 
			buffer[coord(x, z, y - 1)] , 
			buffer[coord(x, z - 1, y - 1)], 
			&c[0],
			&b[0]);
		calcLight(buffer[coord(x, z, y)], 
			buffer[coord(x, z + 1, y)], 
			buffer[coord(x, z, y - 1)] , 
			buffer[coord(x, z + 1, y - 1)], 
			&c[1],
			&b[1]);
		calcLight(buffer[coord(x, z, y)], 
			buffer[coord(x, z - 1, y)], 
			buffer[coord(x, z, y + 1)] , 
			buffer[coord(x, z - 1, y + 1)], 
			&c[2],
			&b[2]);
		calcLight(buffer[coord(x, z, y)], 
			buffer[coord(x, z + 1, y)], 
			buffer[coord(x, z, y + 1)] , 
			buffer[coord(x, z + 1, y + 1)], 
			&c[3],
			&b[3]);
	} else if (facing  & (FACEFRONT | FACEBACK)) {
		/* FRONT / BACK */
		calcLight(buffer[coord(x, z, y)], 
			buffer[coord(x - 1, z, y)], 
			buffer[coord(x, z, y - 1)] , 
			buffer[coord(x - 1, z, y - 1)], 
			&c[0],
			&b[0]);
		calcLight(buffer[coord(x, z, y)], 
			buffer[coord(x + 1, z, y)], 
			buffer[coord(x, z, y - 1)] , 
			buffer[coord(x + 1, z, y - 1)], 
			&c[1],
			&b[1]);
		calcLight(buffer[coord(x, z, y)], 
			buffer[coord(x - 1, z, y)], 
			buffer[coord(x, z, y + 1)] , 
			buffer[coord(x - 1, z, y + 1)], 
			&c[2],
			&b[2]);
		calcLight(buffer[coord(x, z, y)], 
			buffer[coord(x + 1, z, y)], 
			buffer[coord(x, z, y + 1)] , 
			buffer[coord(x + 1, z, y + 1)], 
			&c[3],
			&b[3]);
	} else if (facing  & (FACEDOWN | FACEUP)) {
		/* DOWN / UP */
		calcLight(buffer[coord(x, z, y)], 
			buffer[coord(x - 1, z, y)], 
			buffer[coord(x, z - 1, y)] , 
			buffer[coord(x - 1, z - 1, y)], 
			&c[0],
			&b[0]);
		calcLight(buffer[coord(x, z, y)], 
			buffer[coord(x + 1, z, y)], 
			buffer[coord(x, z - 1, y)] , 
			buffer[coord(x + 1, z - 1, y)], 
			&c[1],
			&b[1]);
		calcLight(buffer[coord(x, z, y)], 
			buffer[coord(x - 1, z, y)], 
			buffer[coord(x, z + 1, y)] , 
			buffer[coord(x - 1, z + 1, y)], 
			&c[2],
			&b[2]);
		calcLight(buffer[coord(x, z, y)], 
			buffer[coord(x + 1, z, y)], 
			buffer[coord(x, z + 1, y)] , 
			buffer[coord(x + 1, z + 1, y)], 
			&c[3],
			&b[3]);
	}
}

Block Chunk::getBlock(int x, int z, int y) {
	if ((x > 15) || (z > 15) || (y > 15) || (x < 0) || (z < 0) || (y < 0)) {
		return pRegion->getBlock(x + _x, z + _z, y + _y);
	} else
		return pBlocks[x + (z << 4) + (y << 8)];
}

Block * Chunk::getpBlock(int x, int z, int y) {
	if ((x > 15) || (z > 15) || (y > 15) || (x < 0) || (z < 0) || (y < 0)) {
		return pRegion->getpBlock(x + _x, z + _z, y + _y);
	} else
		return pBlocks + x + (z << 4) + (y << 8);
}

Block Chunk::breakBlock(int x, int z, int y) {
	if ((x > 15) || (z > 15) || (y > 15) || (x < 0) || (z < 0) || (y < 0)) {
		return pRegion->breakBlock(x + _x, z + _z, y + _y);
	} else {
		Block oldBlock = pBlocks[x + (z << 4) + (y << 8)];
		setBlock(x, z, y, AIR_BLOCK, UPDATE_GEOMETRY | UPDATE_LIGHT);
		return oldBlock;
	}
}

void Chunk::setBlock(int x, int z, int y, Block block, byte flags) {
	if ((x > 15) || (z > 15) || (y > 15) || (x < 0) || (z < 0) || (y < 0)) {
		pRegion->setBlock(x + _x, z + _z, y + _y, block, flags);
	} else {
		Block oldBlock = pBlocks[x + (z << 4) + (y << 8)];

		if ((flags & UPDATE_IF_AIR)  && (oldBlock.blockID != AIR)) return;
		if ((flags & UPDATE_IFN_AIR) && (oldBlock.blockID == AIR)) return;

		pBlocks[x + (z << 4) + (y << 8)] = block;

		if (flags & UPDATE_LIGHT) {
			/* new transparent block */
			if (!(block.status & SOLID)) {
				{
					vector<INTVECTOR3> plist;
					/* old block had light */
					if (oldBlock.blockLight > block.blockLight)  {
						deLight(x, z, y, FACEALL, &plist, flags & ~UPDATE_LIGHT, true);
						reLight(&plist, flags & ~UPDATE_LIGHT, true);
					} else {

					}
					Block adjacentBlock[6];
					adjacentBlock[0] = getBlock(x-1, z, y);
					adjacentBlock[1] = getBlock(x+1, z, y);
					adjacentBlock[2] = getBlock(x, z-1, y);
					adjacentBlock[3] = getBlock(x, z+1, y);
					adjacentBlock[4] = getBlock(x, z, y-1);
					adjacentBlock[5] = getBlock(x, z, y+1);

					char lup = adjacentBlock[5].skyLight;
					if (lup < 15) lup--;
					char ldv = (block.status & LDV) + 1; 
					char lights[7] = {block.skyLight,
						adjacentBlock[0].skyLight - ldv,
						adjacentBlock[1].skyLight - ldv,
						adjacentBlock[2].skyLight - ldv,
						adjacentBlock[3].skyLight - ldv,
						adjacentBlock[4].skyLight - ldv,
						lup - ldv + 1};

					enLight(x, z, y, FACEALL, maxLight(7, lights), flags & ~UPDATE_LIGHT, false);

					char blights[7] = {block.blockLight,
						adjacentBlock[0].blockLight - ldv,
						adjacentBlock[1].blockLight - ldv,
						adjacentBlock[2].blockLight - ldv,
						adjacentBlock[3].blockLight - ldv,
						adjacentBlock[4].blockLight - ldv,
						adjacentBlock[5].blockLight - ldv};

					enLight(x, z, y, FACEALL, maxLight(7, blights), flags & ~UPDATE_LIGHT, true);
				}
				/* new solid block */
			} else {
				vector<INTVECTOR3> plist;
				/* old block had light */

				if (oldBlock.blockLight > block.blockLight) {
					deLight(x, z, y, FACEALL, &plist, flags & ~UPDATE_LIGHT, true);
					reLight(&plist, flags & ~UPDATE_LIGHT, true);
					plist.clear();
				}
				if (oldBlock.skyLight > block.skyLight) {
					deLight(x, z, y, FACEALL, &plist, flags & ~UPDATE_LIGHT, false);
					reLight(&plist, flags & ~UPDATE_LIGHT, false);
				}

				/* new block has light */
				if (block.blockLight) {
					enLight(x, z, y, FACEALL, block.blockLight, flags & ~UPDATE_LIGHT, true);
				}
			}
		}

#ifdef RENDER
		if (flags & UPDATE_GEOMETRY) {
			/* DIRTY */
			bool immediatly =  (flags & UPDATE_IMMEDIATLY) == UPDATE_IMMEDIATLY;
			pRegion->setDirty(x + _x, z + _z, y + _y, immediatly);
			if (x == 0)
				pRegion->setDirty(x + _x - 1, z + _z, y + _y, immediatly);
			else if (x == 15)
				pRegion->setDirty(x + _x + 1, z + _z, y + _y, immediatly);
			if (z == 0)
				pRegion->setDirty(x + _x, z + _z - 1, y + _y, immediatly);
			else if (z == 15)
				pRegion->setDirty(x + _x, z + _z + 1, y + _y, immediatly);
			if (y == 0)
				pRegion->setDirty(x + _x, z + _z, y + _y - 1, immediatly);
			else if (y == 15)
				pRegion->setDirty(x + _x, z + _z, y + _y + 1, immediatly);
		}
#endif
	}
}

void Chunk::update() {
	if (!empty) {
		if ((rand() % 4) == 0)
			for (char x = 0; x < 16; x++) {
				for (char z = 0; z < 16; z++) {
					for (char y = 0; y < 16; y++) {
						if ((rand() % 4) == 0)
							blockUpdate(this, rand(), pBlocks[x + (z << 4) + (y << 8)], x, z, y);
					}
				}
			}
	}
}

byte Chunk::updateBlock(int x, int z, int y, byte type, byte * ticks) {
	if ((x > 15) || (z > 15) || (y > 15) || (x < 0) || (z < 0) || (y < 0)) {
		return pRegion->updateBlock(x + _x, z + _z, y + _y, type, ticks);
	} else {
		Block block = pBlocks[x + (z << 4) + (y << 8)];
		/*
		auto it = BLOCKUPDFUNCTION.find(block.blockID);
		if ((it != BLOCKUPDFUNCTION.end()) || ((block.status & SOLID) && ((it = BLOCKUPDFUNCTION.find(254)) != BLOCKUPDFUNCTION.end())))
			return it->second(&x, &z, &y, this, block, type, ticks);
		else*/
		return block.Update(&x, &z, &y, this, type, ticks);
	}
}

void Chunk::delayedUpdateBlock(int x, int z, int y, byte type, byte ticks)  {
	pRegion->delayedUpdateBlock(x + _x, z + _z, y + _y, type, ticks);
}

void Chunk::deLight(int x, int z, int y, char allowedFacing, vector<INTVECTOR3> * plist, byte flags, bool takeblocklight) {

	Block oldBlock = getBlock(x, z, y);

	// Add new Blocks to the list
	INTVECTOR3 pos = INTVECTOR3(x,z,y);
	if (find(plist->begin(), plist->end(), pos) == plist->end())
		plist->push_back(pos);

	if (takeblocklight) {
		char blocklight = oldBlock.blockLight;
		if ((allowedFacing & FACEALL) == FACEALL) { 
			blocklight = 15;
		}
		// Block is dark -> no light source effects it	
		if (blocklight == 0) {
			return;
		}

		if (!(oldBlock.status & SOLID)) {
			switch (oldBlock.blockID)
			{
			case 40:
				oldBlock.blockLight = 14;
				break;
			default:
				oldBlock.blockLight = 0;
				break;
			}
			setBlock(x, z, y, oldBlock, flags);
		}

		Block adjacentBlock[6];
		adjacentBlock[0] = getBlock(x-1, z, y);
		adjacentBlock[1] = getBlock(x+1, z, y);
		adjacentBlock[2] = getBlock(x, z-1, y);
		adjacentBlock[3] = getBlock(x, z+1, y);
		adjacentBlock[4] = getBlock(x, z, y-1);
		adjacentBlock[5] = getBlock(x, z, y+1);

		if (allowedFacing & FACELEFT) {
			if (blocklight > adjacentBlock[0].blockLight)
				deLight(x-1, z, y, FACEALL & ~FACERIGHT, plist, flags, takeblocklight);
		};
		if (allowedFacing & FACERIGHT) {
			if (blocklight > adjacentBlock[1].blockLight)
				deLight(x+1, z, y, FACEALL & ~FACELEFT, plist, flags, takeblocklight);
		};
		if (allowedFacing & FACEFRONT) {
			if (blocklight > adjacentBlock[2].blockLight)
				deLight(x, z-1, y, FACEALL & ~FACEBACK, plist, flags, takeblocklight);
		};
		if (allowedFacing & FACEBACK) {
			if (blocklight > adjacentBlock[3].blockLight)
				deLight(x, z+1, y, FACEALL & ~FACEFRONT, plist, flags, takeblocklight);
		};
		if (allowedFacing & FACEDOWN) {
			if (blocklight > adjacentBlock[4].blockLight)
				deLight(x, z, y-1, FACEALL & ~FACEUP, plist, flags, takeblocklight);
		};
		if (allowedFacing & FACEUP) {
			if (blocklight > adjacentBlock[5].blockLight)
				deLight(x, z, y+1, FACEALL & ~FACEDOWN, plist, flags, takeblocklight);
		};
	} else {
		char skylight = oldBlock.skyLight;
		if ((allowedFacing & FACEALL) == FACEALL) { 
			skylight = 15;
		}
		// Block ist dark -> no light source effects it	
		if (skylight == 0) {
			return;
		}

		if (!(oldBlock.status & SOLID)) {
			oldBlock.skyLight = 0;
			setBlock(x, z, y, oldBlock, flags);
		}

		Block adjacentBlock[6];
		adjacentBlock[0] = getBlock(x-1, z, y);
		adjacentBlock[1] = getBlock(x+1, z, y);
		adjacentBlock[2] = getBlock(x, z-1, y);
		adjacentBlock[3] = getBlock(x, z+1, y);
		adjacentBlock[4] = getBlock(x, z, y-1);
		adjacentBlock[5] = getBlock(x, z, y+1);

		if (allowedFacing & FACELEFT) {
			if (skylight > adjacentBlock[0].skyLight)
				deLight(x-1, z, y, FACEALL & ~FACERIGHT, plist, flags, takeblocklight);
		};
		if (allowedFacing & FACERIGHT) {
			if (skylight > adjacentBlock[1].skyLight) 
				deLight(x+1, z, y, FACEALL & ~FACELEFT, plist, flags, takeblocklight);
		};
		if (allowedFacing & FACEFRONT) {
			if (skylight > adjacentBlock[2].skyLight) 
				deLight(x, z-1, y, FACEALL & ~FACEBACK, plist, flags, takeblocklight);
		};
		if (allowedFacing & FACEBACK) {
			if (skylight > adjacentBlock[3].skyLight) 
				deLight(x, z+1, y, FACEALL & ~FACEFRONT, plist, flags, takeblocklight);
		};
		if (allowedFacing & FACEDOWN) {
			if (skylight >= adjacentBlock[4].skyLight) 
				deLight(x, z, y-1, FACEALL & ~FACEUP, plist, flags, takeblocklight);
		};
		if (allowedFacing & FACEUP) {
			if (skylight > adjacentBlock[5].skyLight) 
				deLight(x, z, y+1, FACEALL & ~FACEDOWN, plist, flags, takeblocklight);
		};
	}
}

void Chunk::enLight(int x, int z, int y, char allowedFacing, char recursivDepth, byte flags, bool takeblocklight) {
	// recursivdepth reached
	if (recursivDepth == 0) {
		return;
	}

	Block oldBlock = getBlock(x, z, y);

	// Block circles
	if (allowedFacing != FACEALL) {
		if ( takeblocklight) {
			if (recursivDepth <= oldBlock.blockLight) return;
		} else { 
			if (recursivDepth <= oldBlock.skyLight) return;
		}
	}

	Block block;
	char ldv = (oldBlock.status & LDV) + 1; 

	if (takeblocklight) {
		oldBlock.blockLight = recursivDepth;
		setBlock(x, z, y, oldBlock, flags);	

		if (allowedFacing & FACELEFT) {
			block = getBlock(x-1, z, y);
			if (!(block.status & SOLID) && (block.blockLight < recursivDepth))
				enLight(x-1, z, y, FACEALL & ~FACERIGHT, recursivDepth-ldv, flags, takeblocklight);
		};
		if (allowedFacing & FACERIGHT) {
			block = getBlock(x+1, z, y);
			if (!(block.status & SOLID) && (block.blockLight < recursivDepth))
				enLight(x+1, z, y, FACEALL & ~FACELEFT, recursivDepth-ldv, flags, takeblocklight);
		};
		if (allowedFacing & FACEFRONT) {
			block = getBlock(x, z-1, y);
			if (!(block.status & SOLID) && (block.blockLight < recursivDepth))
				enLight(x, z-1, y, FACEALL & ~FACEBACK, recursivDepth-ldv, flags, takeblocklight);
		};
		if (allowedFacing & FACEBACK) {
			block = getBlock(x, z+1, y);
			if (!(block.status & SOLID) && (block.blockLight < recursivDepth))
				enLight(x, z+1, y, FACEALL & ~FACEFRONT, recursivDepth-ldv, flags, takeblocklight);
		};
		if (allowedFacing & FACEDOWN) {
			block = getBlock(x, z, y-1);
			if (!(block.status & SOLID) && (block.blockLight < recursivDepth))
				enLight(x, z, y-1, FACEALL & ~FACEUP, recursivDepth-ldv, flags, takeblocklight);
		};
		if (allowedFacing & FACEUP) {
			block = getBlock(x, z, y+1);
			if (!(block.status & SOLID) && (block.blockLight < recursivDepth))
				enLight(x, z, y+1, FACEALL & ~FACEDOWN, recursivDepth-ldv, flags, takeblocklight);
		};

	} else {

		if (allowedFacing & FACELEFT) {
			block = getBlock(x-1, z, y);
			if (!(block.status & SOLID) && (block.skyLight < recursivDepth))
				enLight(x-1, z, y, FACEALL & ~FACERIGHT, recursivDepth-ldv, flags, takeblocklight);
		};
		if (allowedFacing & FACERIGHT) {
			block = getBlock(x+1, z, y);
			if (!(block.status & SOLID) && (block.skyLight < recursivDepth))
				enLight(x+1, z, y, FACEALL & ~FACELEFT, recursivDepth-ldv, flags, takeblocklight);
		};
		if (allowedFacing & FACEFRONT) {
			block = getBlock(x, z-1, y);
			if (!(block.status & SOLID) && (block.skyLight < recursivDepth))
				enLight(x, z-1, y, FACEALL & ~FACEBACK, recursivDepth-ldv, flags, takeblocklight);
		};
		if (allowedFacing & FACEBACK) {
			block = getBlock(x, z+1, y);
			if (!(block.status & SOLID) && (block.skyLight < recursivDepth))
				enLight(x, z+1, y, FACEALL & ~FACEFRONT, recursivDepth-ldv, flags, takeblocklight);
		};
		if (allowedFacing & FACEDOWN) {
			block = getBlock(x, z, y-1);
			if (!(block.status & SOLID) && (block.skyLight < recursivDepth))
				if (recursivDepth == 15)
					enLight(x, z, y-1, FACEALL & ~FACEUP, recursivDepth - ldv + 1, flags, takeblocklight);
				else
					enLight(x, z, y-1, FACEALL & ~FACEUP, recursivDepth-ldv, flags, takeblocklight);
		};
		if (allowedFacing & FACEUP) {
			block = getBlock(x, z, y+1);
			if (!(block.status & SOLID) && (block.skyLight < recursivDepth))
				enLight(x, z, y+1, FACEALL & ~FACEDOWN, recursivDepth-ldv, flags, takeblocklight);
		};

		oldBlock.skyLight = max(recursivDepth, oldBlock.skyLight);
		setBlock(x, z, y, oldBlock, flags);
	}
};

void Chunk::reLight(vector<INTVECTOR3> * plist, byte flags, bool takeblocklight) {
	/* Sorting makes the function reliable */
	sort (plist->begin(), plist->end());

	for (auto it = plist->rbegin(); it != plist->rend(); it++) {

		INTVECTOR3 pos = (*it);
		int ix = pos.x;
		int iz = pos.z;
		int iy = pos.y;

		Block b = getBlock(ix, iz, iy);
		if (!(b.status & SOLID)) {
			Block blocks[3] = {
				getBlock(ix+1, iz, iy),
				getBlock(ix, iz+1, iy),
				getBlock(ix, iz, iy+1)
			};

			char ldv = (b.status & LDV) + 1; 
			if (!takeblocklight) {

				char lup = blocks[2].skyLight;
				if (lup < 15) lup--;

				char lights[4] = {b.skyLight,
					blocks[0].skyLight - ldv,
					blocks[1].skyLight - ldv,
					lup - ldv + 1};
				b.skyLight = maxLight(4, lights);

			} else {

				/* Blocklight next */
				char blights[4] = {b.blockLight,
					blocks[0].blockLight - ldv,
					blocks[1].blockLight - ldv,
					blocks[2].blockLight - ldv};

				b.blockLight = maxLight(4, blights);

			}
			setBlock(ix, iz, iy, b, flags);
		}
	};

	for (auto it = plist->begin(); it != plist->end(); it++) {

		INTVECTOR3 pos = (*it);
		int ix = pos.x;
		int iz = pos.z;
		int iy = pos.y;

		Block b = getBlock(ix, iz, iy);
		if (!(b.status & SOLID)) {
			Block blocks[3] = {
				getBlock(ix-1, iz, iy),
				getBlock(ix, iz-1, iy),
				getBlock(ix, iz, iy-1)
			};

			char ldv = (b.status & LDV) + 1; 
			if (!takeblocklight) {

				char lights[4] = {b.skyLight,
					blocks[0].skyLight - ldv,
					blocks[1].skyLight - ldv,
					blocks[2].skyLight - ldv};

				b.skyLight = maxLight(4, lights);

			} else {

				/* Blocklight next */
				char blights[4] = {b.blockLight,
					blocks[0].blockLight - ldv,
					blocks[1].blockLight - ldv,
					blocks[2].blockLight - ldv};

				b.blockLight = maxLight(4, blights);

			}
			setBlock(ix, iz, iy, b, flags);
		}
	};
}

#ifdef RENDER

inline void pushVertices(BlockVertex * face, float * c, float * b, vector<BlockVertex> * vbl, vector<WORD> * ibl, WORD i, bool reversed) {
	vbl->push_back(face[0]);
	vbl->push_back(face[1]);
	vbl->push_back(face[2]);
	vbl->push_back(face[3]);

	if (((c[0] < c[3]) && (c[1] < c[3]) &&  (c[2] < c[3])) || ((c[3] < c[0]) && (c[1] < c[0]) &&  (c[2] < c[0])) || 
		((b[0] < b[3]) && (b[1] < b[3]) &&  (b[2] < b[3])) || ((b[3] < b[0]) && (b[1] < b[0]) &&  (b[2] < b[0]))) {
			if (reversed) {
				ibl->push_back(3 + i);
				ibl->push_back(1 + i);
				ibl->push_back(0 + i);

				ibl->push_back(0 + i);
				ibl->push_back(2 + i);
				ibl->push_back(3 + i);
			} else {
				ibl->push_back(0 + i);
				ibl->push_back(1 + i);
				ibl->push_back(3 + i);

				ibl->push_back(3 + i);
				ibl->push_back(2 + i);
				ibl->push_back(0 + i);
			}
	} else {
		if (reversed) {
			ibl->push_back(2 + i);
			ibl->push_back(1 + i);
			ibl->push_back(0 + i);

			ibl->push_back(3 + i);
			ibl->push_back(1 + i);
			ibl->push_back(2 + i);
		} else {
			ibl->push_back(0 + i);
			ibl->push_back(1 + i);
			ibl->push_back(2 + i);

			ibl->push_back(2 + i);
			ibl->push_back(1 + i);
			ibl->push_back(3 + i);
		}
	}
}

inline void pushVertices(BlockVertex * face, WORD size, vector<BlockVertex> * vbl, vector<WORD> * ibl, WORD i) {
	for (int n = 0; n < size; n++) {
		vbl->push_back(face[n]);
	}

	while (size >= 4) {
		ibl->push_back(0 + i);
		ibl->push_back(1 + i);
		ibl->push_back(2 + i);

		ibl->push_back(2 + i);
		ibl->push_back(1 + i);
		ibl->push_back(3 + i);

		size	-= 4;
		i		+= 4;
	}
}

void Chunk::setDirty(int x, int z, int y, bool immediatly) {
	pRegion->setDirty(x + _x, z + _z, y + _y, immediatly);
}

void Chunk::generateVB(ID3D11Device* pd3dDevice, ID3D11DeviceContext* context) {
	genBuffer * buffer		= (genBuffer*)   calloc(5832, sizeof(genBuffer)); 
	BlockVertex * vertex	= (BlockVertex*) calloc(64,   sizeof(BlockVertex)); 

	for (char x = -1; x < 17; x++) {
		for (char z = -1; z < 17; z++) {
			float temperature	= getTemperature(x, z);
			float humidity		= getHumidity(x, z);
			for (char y = -1; y < 17; y++) {
				//Block b = getBlock(x, z, y);
				/*
				genBuffer buf = {
					!(b.status & SOLID),
					(b.status & LIQUID) == LIQUID,
					b.blockID,
					b.skyLight,
					b.blockLight,
					b.metadata,

					temperature,
					humidity,
				};			*/				
				buffer[coord(x+1, z+1, y+1)] = getBlock(x, z, y).toBuffer(temperature, humidity);
			}
		}
	}

	/* Normal Vertexbuffer */
	vector<BlockVertex> vbl[2];
	vector<WORD>		ibl[2]; 
	WORD				i[2] = {0, 0};

	for (char x = 1; x < 17; x++) {
		float xp = static_cast<float>(x + (_x) - 1);

		for (char z = 1; z < 17; z++) {
			float zp = static_cast<float>(z + (_z) - 1);

			D3DXVECTOR3 color	= BiomeColor(buffer[coord(x, z, 0)].temperature, buffer[coord(x, z, 0)].humidity);
			for (char y = 1; y < 17; y++) {
				char bid		= buffer[coord(x, z, y)].id;


				if (bid == AIR) {
					continue;
				} else {
					empty = false;

					float yp = static_cast<float>(y + (_y) - 1);

					WORD size = 0;
					int type  = 0;
					if ((type = Block::Generate(bid, x, z, y, xp, zp, yp, buffer, vertex, &size)) != -1) {
						/*
					auto it = BLOCKGENFUNCTION.find(bid);
					if (it != BLOCKGENFUNCTION.end()) {				
						BLOCKGEN func = it->second;

						WORD size = 0;
						byte type = func(x, z, y, xp, zp, yp, buffer, vertex, &size);
						*/
						pushVertices(vertex, size, &vbl[type], &ibl[type], i[type]);
						i[type] += size;

					} else {

						D3DXVECTOR3 min = D3DXVECTOR3(-0.5f, -0.5f, -0.5f);
						D3DXVECTOR3 max = D3DXVECTOR3( 0.5f,  0.5f,  0.5f);

						min += D3DXVECTOR3(xp, yp, zp);
						max += D3DXVECTOR3(xp, yp, zp);

						float c[4];
						float b[4];

						if (buffer[coord(x-1, z, y)].face) {
							setLight(x-1, z, y, FACELEFT, buffer, c, b);
							D3DXVECTOR2 * uv = getBlockUV(bid, FACELEFT, buffer[coord(x, z, y)].metadata);
							BlockVertex face[] = 
							{
								{D3DXVECTOR3(min.x, min.y, min.z), uv[0], c[0], b[0], color},
								{D3DXVECTOR3(min.x, min.y, max.z), uv[1], c[1], b[1], color},				
								{D3DXVECTOR3(min.x, max.y, min.z), uv[2], c[2], b[2], color},
								{D3DXVECTOR3(min.x, max.y, max.z), uv[3], c[3], b[3], color},
							};
							pushVertices(face, c, b, &vbl[0], &ibl[0], i[0], false);
							i[0] += 4;
						}
						if (buffer[coord(x+1, z, y)].face) {
							setLight(x+1, z, y, FACERIGHT, buffer, c, b);
							D3DXVECTOR2 * uv = getBlockUV(bid, FACERIGHT, buffer[coord(x, z, y)].metadata);
							BlockVertex face[] = 
							{
								{D3DXVECTOR3(max.x, min.y, min.z), uv[0], c[0], b[0], color},
								{D3DXVECTOR3(max.x, min.y, max.z), uv[1], c[1], b[1], color},					
								{D3DXVECTOR3(max.x, max.y, min.z), uv[2], c[2], b[2], color},
								{D3DXVECTOR3(max.x, max.y, max.z), uv[3], c[3], b[3], color},
							};
							pushVertices(face, c, b, &vbl[0], &ibl[0], i[0], true);
							i[0] += 4;
						}

						if (buffer[coord(x, z-1, y)].face) {
							setLight(x, z-1, y, FACEBACK, buffer, c, b);
							D3DXVECTOR2 * uv = getBlockUV(bid, FACEBACK, buffer[coord(x, z, y)].metadata);
							BlockVertex face[] = 
							{
								{D3DXVECTOR3(min.x, min.y, min.z), uv[0], c[0], b[0], color},
								{D3DXVECTOR3(max.x, min.y, min.z), uv[1], c[1], b[1], color},					
								{D3DXVECTOR3(min.x, max.y, min.z), uv[2], c[2], b[2], color},
								{D3DXVECTOR3(max.x, max.y, min.z), uv[3], c[3], b[3], color},
							};
							pushVertices(face, c, b, &vbl[0], &ibl[0], i[0], true);
							i[0] += 4;
						}

						if (buffer[coord(x, z+1, y)].face) {
							setLight(x, z+1, y, FACEFRONT, buffer, c, b);
							D3DXVECTOR2 * uv = getBlockUV(bid, FACEFRONT, buffer[coord(x, z, y)].metadata);
							BlockVertex face[] = 
							{
								{D3DXVECTOR3(min.x, min.y, max.z), uv[0], c[0], b[0], color},
								{D3DXVECTOR3(max.x, min.y, max.z), uv[1], c[1], b[1], color},					
								{D3DXVECTOR3(min.x, max.y, max.z), uv[2], c[2], b[2], color},
								{D3DXVECTOR3(max.x, max.y, max.z), uv[3], c[3], b[3], color},
							};
							pushVertices(face, c, b, &vbl[0], &ibl[0], i[0], false);
							i[0] += 4;
						}

						if (buffer[coord(x, z, y-1)].face) {
							setLight(x, z, y-1, FACEDOWN, buffer, c, b);
							D3DXVECTOR2 * uv = getBlockUV(bid, FACEDOWN, buffer[coord(x, z, y)].metadata);
							BlockVertex face[] = 
							{
								{D3DXVECTOR3(min.x, min.y, min.z), uv[0], c[0], b[0], color},
								{D3DXVECTOR3(max.x, min.y, min.z), uv[1], c[1], b[1], color},					
								{D3DXVECTOR3(min.x, min.y, max.z), uv[2], c[2], b[2], color},
								{D3DXVECTOR3(max.x, min.y, max.z), uv[3], c[3], b[3], color},
							};
							pushVertices(face, c, b, &vbl[0], &ibl[0], i[0], false);
							i[0] += 4;
						}

						if (buffer[coord(x, z, y+1)].face) {
							setLight(x, z, y+1, FACEUP, buffer, c, b);
							D3DXVECTOR2 * uv = getBlockUV(bid, FACEUP, buffer[coord(x, z, y)].metadata);
							BlockVertex face[] = 
							{
								{D3DXVECTOR3(min.x, max.y, min.z), uv[0], c[0], b[0], color},
								{D3DXVECTOR3(max.x, max.y, min.z), uv[1], c[1], b[1], color},					
								{D3DXVECTOR3(min.x, max.y, max.z), uv[2], c[2], b[2], color},
								{D3DXVECTOR3(max.x, max.y, max.z), uv[3], c[3], b[3], color},
							};
							pushVertices(face, c, b, &vbl[0], &ibl[0], i[0], true);
							i[0] += 4;
						}
					}
				}
			}
		}
	}

	free( buffer );
	free( vertex );

	if (!vbl[0].empty() || !vbl[1].empty()) {
		{
			ID3D11Buffer*	bufferVertexBuffer;
			ID3D11Buffer*	bufferIndexBuffer;
			WORD buffvertexSize[2];
			WORD buffvertexOffset[2];
			WORD buffindexSize[2];
			for (int n = 0; n < 2; n++) {
				buffvertexSize[n] = static_cast<WORD>(vbl[n].size());
				buffindexSize[n]  = static_cast<WORD>(ibl[n].size());
			}
			buffvertexOffset[0] = 0;
			for (int n = 1; n < 2; n++) {
				if (!vbl[n].empty()) {
					vbl[0].insert( vbl[0].end(), vbl[n].begin(), vbl[n].end());
					ibl[0].insert( ibl[0].end(), ibl[n].begin(), ibl[n].end());

					buffvertexOffset[n] = buffvertexOffset[n - 1 ] + buffvertexSize[n - 1];
				}
			}
			
			UINT vs = static_cast<UINT>(sizeof( BlockVertex ) * vbl[0].size());
			UINT is = static_cast<UINT>(sizeof( WORD ) * ibl[0].size());
			if ((vs == 0) || (is == 0)) return;

#ifdef DEFERRED_UPDATE
			AcquireSRWLockShared(&DEFERRED_LOCK);
			if ((vs > vbSize) || (is > ibSize)) {
				ReleaseSRWLockShared(&DEFERRED_LOCK);
#endif
				D3D11_BUFFER_DESC bd;
				D3D11_SUBRESOURCE_DATA InitData;
				ZeroMemory( &InitData, sizeof(InitData) );

				bd.Usage				= D3D11_USAGE_DEFAULT;
				bd.CPUAccessFlags		= 0;
				bd.BindFlags			= D3D10_BIND_VERTEX_BUFFER;
				bd.ByteWidth			= vs;
				bd.MiscFlags			= 0;
				bd.StructureByteStride	= 0;
				InitData.pSysMem		= &vbl[0][0];

				pd3dDevice->CreateBuffer( &bd, &InitData, &bufferVertexBuffer );

				bd.Usage				= D3D11_USAGE_DEFAULT;
				bd.CPUAccessFlags		= 0;
				bd.BindFlags			= D3D11_BIND_INDEX_BUFFER;
				bd.ByteWidth			= is;
				bd.MiscFlags			= 0;
				bd.StructureByteStride	= 0;
				InitData.pSysMem		= &ibl[0][0];
				pd3dDevice->CreateBuffer( &bd, &InitData, &bufferIndexBuffer );

#ifdef DEFERRED_UPDATE
				AcquireSRWLockExclusive(&DEFERRED_LOCK);
#endif
				SAFE_RELEASE(pVertexBuffer);
				SAFE_RELEASE(pIndexBuffer);
				pVertexBuffer = bufferVertexBuffer;
				pIndexBuffer = bufferIndexBuffer;

				vbSize = vs;
				ibSize = is;
#ifdef DEFERRED_UPDATE
				ReleaseSRWLockExclusive(&DEFERRED_LOCK);	
#endif

#ifdef DEFERRED_UPDATE
			} else {	
				ReleaseSRWLockShared(&DEFERRED_LOCK);
				AcquireSRWLockExclusive(&DEFERRED_LOCK);
				context->UpdateSubresource( pVertexBuffer, 0, NULL, &vbl[0][0], 0, 0 );
				context->UpdateSubresource( pIndexBuffer,  0, NULL, &ibl[0][0], 0, 0 );
				ReleaseSRWLockExclusive(&DEFERRED_LOCK);	
			}
#endif
			WORD offset = 0;
			for (int n = 0; n < 2; n++) {
				indexOffset[n]			= offset;
				indexSize[n]			= buffindexSize[n];
				offset				   += buffindexSize[n];
				vertexOffset[n]			= buffvertexOffset[n];
			}
		}
	} else {
		indexSize[0]			= 0;
		indexSize[1]			= 0;
		vbSize					= 0;
		ibSize					= 0;
	}
	dirty = false;
}

void Chunk::render(const D3DXFRUSTUM frustum, ID3D11DeviceContext* pImmediateContext, int index) {
	if (indexSize[index] == 0) return;

	if (AABBInFrustum(D3DXVECTOR3(_x - 0.5f, _y - 0.5f, _z - 0.5f), D3DXVECTOR3(_x + 15.5f, _y + 15.5f, _z + 15.5f), frustum)) {

		UINT stride = sizeof( BlockVertex );
		UINT offset = 0;

		pImmediateContext->IASetVertexBuffers( 0, 1, &pVertexBuffer, &stride, &offset );
		pImmediateContext->IASetIndexBuffer( pIndexBuffer, DXGI_FORMAT_R16_UINT, 0 );

		pImmediateContext->DrawIndexed( indexSize[index], indexOffset[index], vertexOffset[index]);
	}
}

void Chunk::Release() {
	SAFE_RELEASE( pVertexBuffer );
	SAFE_RELEASE( pIndexBuffer );

	indexSize[0]			= 0;
	indexSize[1]			= 0;
	vbSize					= 0;
	ibSize					= 0;
}

#endif